test(vi-mock): inherit the real @object-ui/plugin-detail surface in 13 vi.mock factories - #8184
Conversation
…3 vi.mock factories
objectui#6892 slice 9. Every frozen `vi.mock('@object-ui/plugin-detail', ...)`
factory now obtains the real module through `importOriginal` and spreads it
FIRST, with the file's existing overrides after it, so a new export on the
barrel can no longer resolve to `undefined` and kill the file during
collection. `@object-ui/plugin-detail` joins the gate's `COVERED_SPECIFIERS`
in the same PR (13 -> 14), which is the documented widening precondition.
Census re-derived through the gate's own `scan()` on 310c0ab: 17 judged,
4 already inheriting, 13 frozen -> 0. All 13 sit in one syntactic shape
(zero-parameter object-literal arrows) across three packages: plugin-gantt 8,
plugin-calendar 4, plugin-tree 1. No test assertion changed.
STEP 0 measured rather than assumed, and the two measurements disagree.
The barrel's graph is the largest this worklist has walked -- 564 modules,
6181 module-scope statements, 127 ComponentRegistry registrations, 99 bare
side-effect imports -- and timed cold in an EMPTY test file `importOriginal`
of it costs a median 8.8s, which is objectui#6580's app-shell STOP range.
Measured in the real files it is roughly free: -0.1s to +1.0s per file,
because those files already import `@object-ui/react` plus their own view
component, whose combined 572-module graph is a strict SUPERSET of the
barrel's 564 (zero marginal modules). The cost of inheriting a barrel is a
property of the consuming file, not of the barrel.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
… no plugin-detail edge
Fix-up on the slice-9 sweep. `check-phantom-dependencies` reads a
type-position `import('@object-ui/plugin-detail')` as a real specifier, and
`packages/plugin-tree` neither declares that package nor needs to: measured,
`ObjectTree`'s module graph reaches ZERO plugin-detail modules, against 50
each from `ObjectGantt` and `ObjectCalendar`. That factory therefore mocks a
module nothing under test ever loads, and the typed spelling would have made
the package declare a dependency its runtime does not have.
`<any>` inherits the same surface without asserting the edge, and it is the
spelling this tree's three already-inheriting plugin-detail factories use. The
twelve siblings keep the typed form, where the edge is real and declared. A
comment on the factory records the measurement so the difference is not read
as drift and "corrected" back.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Standing down on That check is informational (not in the Generated by Claude Code |
|
Armed —
Landing stroke follows the merge: content probe on re-fetched Generated by Claude Code |
|
LANDED — Merged at 2026-09-06T23:30:16Z as Slice 10 ( Generated by Claude Code |
Part of #6892 (slice 9:
@object-ui/plugin-detail)Base
origin/main310c0ab19(carries PR #8163, slice 8). Shipped head60a2c6a51. Every reading below is pinned to that head on a clean tree.The thirteen
vi.mock('@object-ui/plugin-detail', ...)factories that hand-listed the barrel's exports now obtain the real module and spread it FIRST, with each file's existing overrides after it, and@object-ui/plugin-detailjoins the gate'sCOVERED_SPECIFIERSin the same PR. No test assertion changed.STEP 0 — PROCEED, and the isolated probe would have answered it WRONG
Method. Static import graph walked from
packages/plugin-detail/src/index.tsxwith the TypeScript parser, every module-scope statement of every reached module classified (TS ESM'./x.js'resolved to./x.ts/.tsx,'@/x'as the per-package src alias). Corroborated empirically by importing the real barrel throughimportOriginalunder the lightdomproject — the single project all seventeen sites run in.The graph is the largest this worklist has walked: 564 modules, 6,181 module-scope statements, 2,610 import/export, 127
ComponentRegistry.registercalls, 99 bare side-effect imports and one CSS import. The barrel's OWN sixteen registrations are all namespaced (A2 as stated:plugin-detail3,record11,view1,field1) — but A2 is falsified for the transitive graph: the barrel reaches@object-ui/components, whoseimport './renderers'cascade contributes 111 further registrations, five of them BARE (no namespace) atpackages/components/src/renderers/layout/page.tsx:690-695, registeringpage,app,utility,home,record.Import cost, three runs each, median reported.
importOriginalof the barrel, isolated probe fileplugin-ganttrowCeiling, FROZENplugin-calendarrowCeiling, frozen then inheritingplugin-treerowCeiling, frozen then inheritingComparator: objectui#6580 measured
importOriginal('@object-ui/app-shell')at ~10.0 s per console test file, which the dispatch defined as a STOP. The isolated probe reproduces that range, and it is an artefact. Acting on it would have stopped a free conversion.Mechanism, measured rather than argued. All thirteen files already import
@object-ui/reactand their own view component (./ObjectGanttand friends) at module scope. Those two together reach 572 modules — a strict SUPERSET of the barrel's 564, withcomm -23over the two sorted module lists reporting exactly 0 modules that inheriting adds.importOriginalhere re-resolves an already-resident graph; it loads nothing new. The contrast is visible per package:ObjectGantt's graph contains 50 plugin-detail modules andObjectCalendar's 50, so the mock is live in those files.⇒ Carry-forward for slice 10 and beyond: a barrel's import cost is not a property of the barrel. Measure it in the files that will pay it, against what they already load. A probe that imports the barrel alone answers "what does this graph cost from cold", which is the wrong question whenever the consuming file already holds it.
⛔ No change to
packages/plugin-detail/src/**; STEP 0 called for none.STEP 1 — census, through the gate's own exported
scan()Covered set widened in-memory to the 22 workspace specifiers any
vi.mockcall site in the tree names;COVERED_SPECIFIERSwas never widened-and-reverted on disk.packages/plugin-ganttpackages/plugin-calendarpackages/plugin-treepackages/plugin-kanbanpackages/app-shellA1 CONFIRMED exactly, in both halves: the slice-8 forecast of 17 judged / 13 frozen is what
scan()re-derives on310c0ab19, and the PM's per-package file split (gantt 9, calendar 5, one each in app-shell, kanban, tree) is exact. No drift to report.All thirteen sit in one syntactic shape — zero-parameter object-literal arrows — the most uniform surface this worklist has swept. Population moves 74 → 61 frozen over a constant 659 judged. Direction check: a diff of the two per-specifier tables with the plugin-detail row removed is EMPTY, all 21 other rows byte-identical; no site moved the other way.
Free confirmation PRESENT: 4 of 17 sites already inherited the real barrel on
mainand passed, across four packages, so the module was known to load in this environment before anything was converted.Neighbour reading — per file, judged against the graph STEP 0 walked
Every other
vi.mockfactory in all thirteen files was read and classified before editing: 7 frozensonner(third-party, out of scope by construction) and 12 local whole-module replacements (./GanttView8,./CalendarView4, out of scope by the ruling). Slice 6's collection-death class did not fire, and the reason was established in advance rather than by a red run: this barrel's graph reaches neithersonnernor those local modules. Zero neighbouring repairs.A5 CONFIRMED on the base: no file among the thirteen mocks
@object-ui/app-shellor@object-ui/plugin-list(grep exit 1 on both). No hot-file overlap with the parked specifier. ⛔ No file mocking@object-ui/app-shellwas touched.One file diverges, and it is a finding rather than a style choice
packages/plugin-tree/src/ObjectTree.rowCeiling-7210.test.tsxinherits throughimportOriginaltyped as ANY rather than thetypeof import(...)form its twelve siblings use.check-phantom-dependenciesreads a type-positionimport()as a real specifier and reddened on the typed form:packages/plugin-treedoes not declare@object-ui/plugin-detail, and has no reason to. Measured,ObjectTree's module graph reaches 0 plugin-detail modules, against 50 each fromObjectGanttandObjectCalendar— that factory mocks a module nothing under test ever loads.The typed spelling would have forced the package to declare a dependency its runtime does not have. The ANY spelling inherits the same surface without asserting an edge that is not there, and it is what this tree's three already-inheriting plugin-detail factories use. A comment on the factory records the measurement so the difference is not read as drift and "corrected" back. The phantom mock itself is left in place — removing it is a decision for triage, not a rider here.
Positive control — three legs, from the committed tree, restore proven by STATE
Each leg under a
trapwith absolute paths; each mutation proven on disk by an anchored count AND a changed blob before the gate result is read; each restore proven bygit hash-objectequalling the HEAD blob ANDgit diff HEADnaming zero paths — never by a checkout exit code.plugin-gantt/.../ObjectGantt.rowCeiling-7210.test.tsx:64080e6cf56✓plugin-calendar/.../ObjectCalendar.unscheduled-7071.test.tsx:56ac18c89d0✓plugin-tree/.../ObjectTree.rowCeiling-7210.test.tsx:579c768873b✓Gate back to exit 0 after each restore, and
git statusempty at the end.finding):holdsObtainedModule()tests whether the obtained token is MENTIONED inside a spread, not whether the module is the thing spread. Measured on this tree, 0 of the 576 covered sites judgedinheritsuse that shape, so nothing evades the gate today. ⛔ Not repaired here — this PR widens the covered set, it does not touch the recogniser.No dist preflight is owed: the gate is a source-text scanner and both configs alias the specifier to source.
Covered set: 13 → 14
One line added to
COVERED_SPECIFIERS('@object-ui/plugin-detail', exact string match), the header count word thirteen → fourteen, the slice-9 per-specifier record added in the shape slices 5-8 used, and the remaining-population paragraph refreshed 74 → 61 with@object-ui/app-shellre-annotated as PARKED under objectui#8173.The pin
scripts/__tests__/check-vi-mock-inherit.test.tsneeded no edit — confirmed by RUNNING it, not by reading it: it derives names fromCOVERED_SPECIFIERSrather than copying them.Gates — exit codes captured by redirect-then-capture, pinned to
60a2c6a51node scripts/check-vi-mock-inherit.mjs@object-ui/plugin-detail(17 judged, 17 inheriting)scripts/__tests__/check-vi-mock-inherit.test.tsvitest run scripts/__tests__/(whole directory)vitest runthe 13 converted files310c0ab19vitest runplugin-gantt + plugin-calendar + plugin-treetype-checkof the three packagestsconfig.test.json --listFiles: 8/8, 4/4, 1/1 edited files present in programs of 1481, 1402, 1188pnpm type-check:scriptspnpm lint:rooteslint --no-inline-config --format jsonover the 14 non-changeset pathsno-explicit-anyon the plugin-tree ANY spelling, the same rule firing 89 times on untouched lines in these files)pnpm check:phantom-depspnpm check:control-bytesgrep -naPself-scan of all 15 changed paths, grep exit 1, no matchnode scripts/check-changeset-presence.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-governed-queue-guard.mjs --testpnpm check:entry-guardpnpm check:vi-mock-specifiersDependency closure built first (
pnpm --filter '...^...' build, exit 0) — without it the three type-checks fail withTS2307 Cannot find moduleon every workspace import, which reads exactly like a break this diff caused.Live E2E (informational)is base-red on every branch today for an upstream reason (#7990 / objectstack#16186), not this PR.Declared narrowing.
turbo ls --affectednames nine packages; the three OWNING packages were run whole andapps/console,apps/siteand the three examples were not. Backed by evidence rather than judgement: every changed file is a test file, and test files are leaves, so no dependent package has a path to reach this diff. CI runs the full farm regardless.Two commits, no amend and no force-push at any point: the branch history is the empty routing push, the sweep, and the plugin-tree fix-up.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKrGenerated by Claude Code